﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {
    public class Light : Node {
        #region Constant
        public const int Ambient     = 128;
        public const int Directional = 129;
        public const int Omni        = 130;
        public const int Spot        = 131;
        #endregion

        #region Field
        int   mode;
        uint  color;
        float intensity;
        float constantAttenuation;
        float linearAttenuation;
        float quadraticAttenuation;
        float spotAngle;
        float spotExponent;
        #endregion

        #region Method
        public Light () {
            this.mode = Directional;
            this.color = 0x00ffffff;
            this.intensity = 1.0f;
            this.constantAttenuation = 1.0f;
            this.linearAttenuation = 0;
            this.quadraticAttenuation = 0;
            this.spotAngle = 45.0f;
            this.spotExponent = 0;
        }

        protected Light (Light light)
            : base (light) {
            this.mode = light.mode;
            this.color = light.color;
            this.intensity = light.intensity;
            this.constantAttenuation = light.constantAttenuation;
            this.linearAttenuation = light.linearAttenuation;
            this.quadraticAttenuation = light.quadraticAttenuation;
            this.spotAngle = light.spotAngle;
            this.spotExponent = light.spotExponent;
        }

        public uint Color {
            get { return color; }
            set { color = value & 0x00ffffff; }
        }

        public float ConstantAttenuation {
            get { return constantAttenuation; }
        }

        public float Intensity {
            get { return intensity; }
            set {
                var intensity = value;
                if (intensity < 0) {
                    throw new ArgumentException ("Intensity must be positive number or 0, i=" + intensity);
                }

                this.intensity = intensity;
            }
        }

        public float LinearAttenuation {
            get { return linearAttenuation; }
        }

        public int Mode {
            get { return mode; }
            set {
                var mode = value;
                if (mode < Ambient || mode > Spot) {
                    throw new ArgumentException ("Mode must be Ambient, Directional, Omni, Spot, mode=" + mode);
                }

                this.mode = mode;
            }
        }

        public float QuadraticAttenuation {
            get { return quadraticAttenuation; }
        }

        public void SetAttenuation (float constant, float linear, float quadratic) {
            if (constant < 0 || linear < 0 || quadratic < 0) {
                throw new ArgumentException ("Attenuation parameters must be positive, c=" + constant + ",l=" + linear + ",q=" + quadratic);
            }
            if (constant == 0 && linear == 0 && quadratic == 0) {
                throw new ArgumentException ("One of Attenuation parameters must be positive number, c=" + constant + ",l=" + linear + ",q=" + quadratic);
            }

            this.constantAttenuation = constant;
            this.linearAttenuation = linear;
            this.quadraticAttenuation = quadratic;
        }

        public float SpotAngle {
            get {
                return spotAngle;
            }
            set {
                var spotAngle = value;
                if (spotAngle < 0 || spotAngle > 90) {
                    throw new ArgumentException ("Spot angle must be within [0,90], angle=" + spotAngle);
                }

                this.spotAngle = spotAngle;
            }
        }

        public float SpotExponent {
            get { return spotExponent; }
            set {
                var spotExponent = value;
                if (spotExponent < 0 || spotExponent > 128) {
                    throw new ArgumentException ("Spot exponent must be within [0,128], exp=" + spotExponent);
                }

                this.spotExponent = spotExponent;
            }
        }

        public override int Animate (int time) {

            base.Animate (time);

            var animatedColor        = new float[3];
            var animatedIntensity    = new float[1];
            var animatedSpotAngle    = new float[1];
            var animatedSpotExponent = new float[1];
            var value                = new float[3];
            var animated             = new Dictionary<int, bool> (4);

            for (var i=0; i < AnimationTrackCount; i++) {
                var track = AnimationTrack[i];
                var ch    = AnimationTrackChannel[i];
                if (track.Controller == null || !track.Controller.Active (time)) {
                    continue;
                }
                var property = track.TargetProperty;
                var weight   = track.Controller.Weight;

                animated[property] = true;
                switch (property) {
                    case M3G.AnimationTrack.Intensity: {
                        track.Sample (time, ch, value);
                        animatedIntensity[0] = value[0] * weight;
                        break;
                    }
                    case M3G.AnimationTrack.Color: {
                        track.Sample (time, ch, value);
                        animatedColor[0] = value[0] * weight;
                        animatedColor[1] = value[1] * weight;
                        animatedColor[2] = value[2] * weight;
                        break;
                    }
                    case M3G.AnimationTrack.SpotAngle: {
                        track.Sample (time, ch, value);
                        animatedSpotAngle[0] = value[0] * weight;
                        break;
                    }
                    case M3G.AnimationTrack.SpotExponent: {
                        track.Sample (time, ch, value);
                        animatedSpotExponent[0] = value[0] * weight;
                        break;
                    }
                       }// switch
            }  // for

            if (animated[M3G.AnimationTrack.Intensity]) {
                this.intensity = animatedIntensity[0];
            }
            if (animated[M3G.AnimationTrack.Color]) {
                var r = (uint)animatedColor[0].Clamp (0, 1) * 255;
                var g = (uint)animatedColor[1].Clamp (0, 1) * 255;
                var b = (uint)animatedColor[2].Clamp (0, 1) * 255;
                this.color = (r << 16) | (g << 8) | (b << 0);
            }
            if (animated[M3G.AnimationTrack.SpotAngle]) {
                this.spotAngle = animatedSpotAngle[0];
            }
            if (animated[M3G.AnimationTrack.SpotExponent]) {
                this.spotExponent = animatedSpotExponent[0];
            }

            return 0;
        }

        public override Object3D Duplicate () {
            var lgh = new Light (this);
            lgh.parent = null;
            return lgh;
        }
        #endregion
    }
}
